
//Copyright 1997-2009 Syrinx Development, Inc.
//This file is part of the Syrinx Web Application Framework (SWAF).
// == BEGIN LICENSE ==
//
// Licensed under the terms of any of the following licenses at your
// choice:
//
//  - GNU General Public License Version 3 or later (the "GPL")
//    http://www.gnu.org/licenses/gpl.html
//
//  - GNU Lesser General Public License Version 3 or later (the "LGPL")
//    http://www.gnu.org/licenses/lgpl.html
//
//  - Mozilla Public License Version 1.1 or later (the "MPL")
//    http://www.mozilla.org/MPL/MPL-1.1.html
//
// == END LICENSE ==
using System;
using System.Collections;
using System.Runtime.Remoting.Activation;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Serialization.Formatters.Soap;
using System.Runtime.Serialization;
using System.Xml;

using Swaf;
using Swaf.BizObj;
using Swaf.BizRule;
using Swaf.Config;

using Swaf.Container;
using Swaf.Security;

namespace Swaf.Call
{
	/// <summary>
	/// Summary description for RuleMgrCall.
	/// </summary>
	public class RemotingRuleMgrCall : BaseCall
	{
		private static IMap s_remoteApps = new FlexiMap();
		private static bool s_useMultiSession = true;
		private static string s_sessionTimeout = "expire after 10 minutes no use";

		protected string m_url = "";
		protected bool m_throwExceptionFromRule = true;

		public RemotingRuleMgrCall(string url, string methodName, bool throwException)
			: base(methodName)
		{
			m_url = Application.currentApp.resMgr.resolve(url);
			m_throwExceptionFromRule = throwException;
		}

		public RemotingRuleMgrCall(string id, XmlElement initInfo)
			:base(id, initInfo)
		{
			IApplication app = Application.currentApp;
			m_url = app.resMgr.resolve(initInfo.GetAttribute("url"));
		}
		public RemotingRuleMgrCall(IBizObj workerInfo) : base(workerInfo)
		{
			m_url=Application.currentApp.resMgr.resolve((string)workerInfo.get("extraInfo"));
		}
		static RemotingRuleMgrCall()
		{
			ChannelServices.RegisterChannel(new TcpClientChannel("TcpClient", new BinaryClientFormatterSinkProvider()));


			Hashtable options = new Hashtable();
			options["includeVersions"] = false;
			options["strictBinding"] = false;
			SoapClientFormatterSinkProvider soapProvider = new SoapClientFormatterSinkProvider(options, null);
			//soapProvider.TypeFilterLevel = TypeFilterLevel.Full;
			ChannelServices.RegisterChannel(new HttpClientChannel("HttpClient", soapProvider));
			//ChannelServices.RegisterChannel(new HttpClientChannel("HttpClient", new Swaf.Remoting.ClientSinkProvider()));
		}
		public string Url {get{return m_url;}}

		public static bool UseMultiSession
		{
			set{s_useMultiSession = value;}
		}
		public static string SessionTimeout
		{
			set{s_sessionTimeout = value;}
			get{return s_sessionTimeout!=""?s_sessionTimeout: "expire in 10 minutes";}
		}
		private BasicResults getResults(IResults remoteResults)
		{
			XmlDocument doc = new XmlDocument();
			doc.LoadXml(remoteResults.getXml());
			return BasicResults.createFromXml(doc.DocumentElement);

		}
		public override object call(params object[] p)
		{
			IResults remoteCallResults = null;

			try
			{
				// Create the SessionAppManagement Proxy
				ISessionAppManagement sessAppMgmt = (ISessionAppManagement)Activator.GetObject(typeof(ISessionAppManagement), m_url);
				
				// Get info to pass to sessAppMgmt execute call
				ISecurityPrincipal currPrincipal = Application.currentApp.securityMgr.currentPrincipal;

				string currentSessionId = Application.currentApp.sessionId;
				string remoteSessionKey = s_useMultiSession?m_url + currentSessionId : m_url;
				string remoteSessionId = (string)s_remoteApps[remoteSessionKey];
				if (remoteSessionId == null)
				{
					// specify the session Id
					remoteSessionId = s_useMultiSession ? currentSessionId : Guid.NewGuid().ToString();

					// store the session Id
					s_remoteApps.put(remoteSessionKey, remoteSessionId, SessionTimeout);
				}

				bool isTiedToGlobalApp = remoteSessionId.Equals(Application.globalApp.sessionId);
                
                //string currentCulture = Application.currentApp.culture.Name;
				string currentCulture = Application.currentApp.resMgr.CurrentCulture.get("browserCode").ToString();

				object[] callParams = this.calcCallParams(p);

				if (WorksWithMultiCall && callParams != null && callParams.Length > 0 && callParams[0] is MultiCallInfo)
				{
					MultiCallInfo paramsCol = (MultiCallInfo) callParams[0];

					try
					{
						remoteCallResults = sessAppMgmt.executeRule(remoteSessionId, isTiedToGlobalApp, currPrincipal.userName, currPrincipal.additionalInfo, currPrincipal.instanceId, currentCulture,
							m_methodName,paramsCol.RuleParams, true);
						if (m_throwExceptionFromRule && remoteCallResults.ExceptionInfo != null)
						{
							throw new RemotingRuleException(string.Format("Remote rule call failed at url '{0}' for method '{1}'.", m_url, m_methodName), remoteCallResults.ExceptionInfo);
						}
					}
					catch (InvalidSessionException)
					{
						throw;
					}
					catch (RemotingRuleException)
					{
						throw;
					}
					catch (Exception e)
					{
						throw new CallException(string.Format("Failed to execute remote rule at '{0}': '{1}'", m_url, m_methodName), e);
					}
				}
				else
				{
					callParams = getParams(callParams);
					int numAttempts = 0;
					while (numAttempts < 3)
					{
						try
						{
							remoteCallResults = sessAppMgmt.executeRule(remoteSessionId, isTiedToGlobalApp, currPrincipal.userName, currPrincipal.additionalInfo, currPrincipal.instanceId, currentCulture, m_methodName, callParams);
							if (m_throwExceptionFromRule && remoteCallResults.ExceptionInfo != null)
							{
								throw new RemotingRuleException(string.Format("Remote rule call failed at url '{0}' for method '{1}'.", m_url, m_methodName), remoteCallResults.ExceptionInfo);
							}
							break;
						}
						catch (System.Net.WebException)
						{
							numAttempts++;
							System.Threading.Thread.Sleep(100 * numAttempts);
						}
						catch (InvalidSessionException)
						{
							throw;
						}
						catch (RemotingRuleException)
						{
							throw;
						}
						catch (Exception e)
						{
							throw new CallException(string.Format("Failed to execute remote rule at '{0}': '{1}'", m_url, m_methodName), e);
						}
					}
				}
			}
			catch (InvalidSessionException)
			{
				throw;
			}
			catch (RemotingRuleException)
			{
				throw;
			}
			catch (CallException)
			{
				throw;
			}
			catch(Exception e)
			{
				throw new CallException(String.Format("Unable to make call to remote rule at '{0}' named '{1}", m_url, m_methodName), e);
			}

			return m_throwExceptionFromRule ? remoteCallResults.CallResults : remoteCallResults;
		}

		private object[] getParams(object[] p)
		{
			object[] inParms = p;
			try
			{
				if (inParms != null && inParms.Length != 0 && inParms[0] is Swaf.IApplication)
				{
					if (inParms.Length ==3)
					{
						if (inParms[2] == null)
						{
							return null;
						}
						else if (inParms[2] is object[])
						{
							return (object[])inParms[2];
						}
						else
							return new object[] { inParms[2] };
					}
					else
					{
						object[] newParams = new object[inParms.Length - 2];
						Array.Copy(inParms,2,newParams,0,newParams.Length);
						return newParams;
					}
				}
				else
				{
					return p;
				}
			}
			catch(Exception e)
			{
				Application.currentApp.Log.println("Invalid paramters for rule call, received exception: {0}", e);
				throw;
			}
		}
	}

	public class MultiSessionSupport : ConfigHandler, IConfigPartner
	{
		protected static string[] s_legalAttributes = new string[]{"multisession","sessiontimeout"};
		public MultiSessionSupport()
		{
		}
		public MultiSessionSupport(IApplication app)
		{

		}
		public override void  executeBefore(IConfigElement configInfo)
		{
			IApplication app = (IApplication)configInfo.Context["app"];
			configInfo.checkIllegalAttributes(s_legalAttributes);
			string multisession = configInfo.getAttribute("multisession");
			string sessionTimeout = configInfo.getAttribute("sessiontimeout");
			RemotingRuleMgrCall.SessionTimeout=(sessionTimeout != null & sessionTimeout !="") ? sessionTimeout:"expire in 10 minutes";
			RemotingRuleMgrCall.UseMultiSession=(multisession != null && multisession!="") ? bool.Parse(multisession) : false; 
			Application.currentApp.Log.println(Application.VERBOSE_STARTUP_LOG_LEVEL,"Defined RemotingRuleMgrCall: sessionTimeout={0} and multiSession={1}", sessionTimeout, multisession);
		}
		#region IConfigPartner Members

		public void checkConfigStatus()
		{
			// TODO:  Add RemotingRuleMgrCall.checkConfigStatus implementation
		}

		public void registerForConfig(IConfigManager config)
		{
			config.registerHandler("...<SessionManagement>", this);
		}

		#endregion
	}

	/// <summary>
	/// RemotingRuleException thrown when a exception occurs calling a remote rule
	/// </summary>
	[Serializable]
	public class RemotingRuleException : BaseException
	{
		/// <summary>
		/// Default constructor
		/// </summary>
		public RemotingRuleException()
		{
			
		}

		/// <summary>
		/// Standard message constructor
		/// </summary>
		/// <param name="message"></param>
		public RemotingRuleException(string message)
			: base(message)
		{
		}

		/// <summary>
		/// Standard message constructor with an inner exception
		/// </summary>
		/// <param name="message"></param>
		/// <param name="innerException"></param>
		public RemotingRuleException(string message, Exception innerException)
			: base(message, innerException)
		{
		}

		/// <summary>
		/// Deserialization constructor
		/// </summary>
		/// <param name="info"></param>
		/// <param name="context"></param>
		public RemotingRuleException(System.Runtime.Serialization.SerializationInfo info, StreamingContext context)
			: base(info, context)
		{
		}
	}
}
